Back - Trigger

Learning Articles - Website Development

Lacrimae rerum. Memento mori. Memento vivere.

Eleventy Initial Setup

Eleventy is a static site generator and was created to be a JavaScript alternative to Jekyll. It offers a simple and minimal configuration, as well as the possibility for advanced customization options. It is also designed to work with an existing directory structure and can use multiple independent template engines, which aims to decouple the content from the static site generator and avoid holding the content hostage to the static site generator - although this is the intention of the project, these notes still avoid the specialized features and focus on uses which are generalized to always ensure the content is independent and transferable. The project was started and is maintained by Zach Leatherman.

Getting Started Setup

This article was written based on the latest version of Eleventy at the time and is occasionally updated. The dependencies for Eleventy include Node.js 12 or newer and NPM. For most Linux systems, Node.js may already be installed, otherwise it should be available in the respective repository of the distribution. Alternatively, it can be downloaded from the official website. Eleventy is hosted as a scoped public package (@scope/package, rather than unscoped through package) on NPM. The official tutorials and documentation for Eleventy is available from the official website with example projects.

Install Node.js and NPM for Debian from the officially hosted distributions:
~ $ apt install nodejs
										~ $ apt install npm

Local Installation

A local installation is unique to and only accessible from the project directory. Before installing Eleventy into the project directory, it is necessary to create a packages file through Node.js. Eleventy can then be installed locally in the project directory and it is subsequently possible to run Eleventy to process the files in the project directory and build the files to the output site directory. A live version of the site can be seen by starting a hot-reloading web server - usually located at http://localhost:8080/ (internal) or http://192.168.8.XXX:8080/ (external). This hot-reloading web server is facilitated through Browsersync with synchronization options accessible at http://localhost:3001/ or set through the configuration file with other settings for Eleventy (it should be noted that Eleventy does not use the watch options from Browsersync but triggers reloads manually after internal watch methods are complete). Alternatively and when a preview is not required, it is possible to watch for changes to files and build the files to the output site directory without starting the hot-reloading web server.

Create a project directory and packages file through Node.js:
~ $ mkdir project-directory
~ $ cd project-directory
~/project-directory $ npm init -y
Install Eleventy locally through Node.js within the project directory:
~/project-directory $ npm install --save @11ty/eleventy
Run Eleventy locally from the root of the project directory:
~/project-directory $ npx @11ty/eleventy
Start a hot-reloading web server using Browsersync:
~/project-directory $ npx @11ty/eleventy --serve
Watch files to trigger build without a hot-reloading web server:
~/project-directory $ npx @11ty/eleventy --watch

There are several options which can be defined with the execution of Eleventy. The input directory can be assigned to change the top level directory in which templates and content are found. The output directory can be assigned to change the final directory to which the project is built. The process can be effectively deployed to a sub-directory with a path prefix which is inserted at the beginning of all absolute URL hyperlinks (useful when deploying to GitHub or GitLab for hosting at an extended domain). The process can be run with only a sub-set of format or types of the templates which should be processed and transformed. The process can be run to passthrough file copy all content within the input directory. The process can be run without logging information to the console. The process can be run to only incrementally update files which change. The process can be run dryly without actually writing to the file system. A different configuration file can be selected to replace the default file in the root directory. These options will take precedent over any modified definitions in the configuration file.

Other useful command line options compatible with a local installation:
~/project-directory $ npx @11ty/eleventy --input="."

~/project-directory $ npx @11ty/eleventy --output="_output"

~/project-directory $ npx @11ty/eleventy --pathprefix="/base-sub-directory/"

~/project-directory $ npx @11ty/eleventy --formats=html,liquid,ejs,md,hbs,mustache,haml,pug,njk,11ty.js

~/project-directory $ npx @11ty/eleventy --passthroughall

~/project-directory $ npx @11ty/eleventy --quiet

~/project-directory $ npx @11ty/eleventy --incremental

~/project-directory $ npx @11ty/eleventy --dryrun

~/project-directory $ npx @11ty/eleventy --config=.eleventy.js

~/project-directory $ npx @11ty/eleventy --help

Global Installation

When performing global installations, this will usually install the modules at /usr/local/lib/node_modules by default and requires root user privileges for the installation. It would also be necessary to change the Node.js path to look to this global directory each time a global module is required, rather than looking in the local directory for the modules by default. So, there are several additions which can be used for simplification and convenience. The first addition is to change the directory to which global modules are installed and assign this directory in the home directory rather than the default directory - conventionally assigned at ~/.local, which installs global executables to ~/.local/bin/ and modules to ~/.local/lib/node_modules/ (with regards to a local installation, the executables and modules are installed to ./node_modules/.bin/ and ./node_modules respectively within the project directory). The second addition is to change the node path to automatically look to the global directory each time a module is required - conventionally permanently edited within ~/.bashrc. The settings for Node are stored and modified in /home/.npmrc (see default settings).

Check the current global root directory of Node.js:
~ $ npm root --global

~ $ npm config get prefix
Change the global directory location of Node.js:
~ $ npm config set prefix ~/.local
Check the current path for Node.js:
~ $ echo $NODE_PATH
Change the path of Node.js temporarily for the current terminal session:
~ $ export NODE_PATH="~/.local/lib/node_modules/"
Change the path of Node.js permanently in the terminal configuration:
~ $ echo "export NODE_PATH="~/.local/lib/node_modules/"" >> ~/.bashrc
List the globally installed modules of Node.js:
~ $ npm list --global

~ $ npm list --global | head -1

~ $ npm list --global --depth=0

Although it is not usually recommended, it is possible to install Eleventy globally to be used directly in any directory without needing to install it locally. However, it should be noted that a local installation will cause fewer interoperability issues later if Eleventy is used on multiple projects which use different versions. If it is planned to deploy the site using a service which will run the build on its servers, it is also imperative to use a local installation method. If installed globally, it is not necessary to create a packages file through Node.js unless other dependencies are required which are only installed locally. This is actually the method personally preferred to keep the local directory clean and organized.

Install Eleventy globally through Node.js to be accessed in any directory:
~ $ npm install --global @11ty/eleventy
Run Eleventy globally from the root of the project directory:
~/project-directory $ eleventy
Other useful command line options compatibile with a global installation:
~/project-directory $ eleventy --serve

~/project-directory $ eleventy --watch

~/project-directory $ eleventy --input="."

~/project-directory $ eleventy --output="_output"

~/project-directory $ eleventy --pathprefix="/base-sub-directory/"

~/project-directory $ eleventy --formats=html,liquid,ejs,md,hbs,mustache,haml,pug,njk,11ty.js

~/project-directory $ eleventy --passthroughall

~/project-directory $ eleventy --quiet

~/project-directory $ eleventy --incremental

~/project-directory $ eleventy --dryrun

~/project-directory $ eleventy --help

Running Eleventy in debug mode can be used to view the directories being used for templates, data, includes, input, and output. This mode uses the debug environment variable from the Debug package from NPM (automatically installed). This can also be useful to execute with a dry run without writing to the file system. Furthermore, it is possible to also filter for benchmark messages to gauge the performance when building with measurements, but it should be noted that the measurements may not be completely accurate due to the asynchronous nature of the tasks (by default, there will be warnings if certain pieces of the build take longer than 8% of the total build time).

View debugging messages outputted from Eleventy:
~/project-directory $ DEBUG=Eleventy* npx @11ty/eleventy

~/project-directory $ DEBUG=Eleventy* eleventy
View debugging messages outputted from everything in Node.js:
~/project-directory $ DEBUG=* npx @11ty/eleventy

~/project-directory $ DEBUG=* eleventy
Evaluate the performance through debugging (may be inaccurate):
~/project-directory $ DEBUG=Eleventy:Benchmark* npx @11ty/eleventy

~/project-directory $ DEBUG=Eleventy:Benchmark* eleventy

Configuration File

If it is desired to change the default configuration, it is necessary to create .eleventy.js in the root of the project directory. The configuration can be equivalently exposed as an object literal or function. The input directory can be assigned to change the top level directory in which templates and content are found. The output directory can be assigned to change the final directory to which the project is built. The includes directory can be assigned to change the directory for assets which are used repeatedly for multiple pages and layouts (must be located relative to the input directory). The layouts directory can be assigned to change the directory for special templates which are used to wrap other content - defaults to the same value as the includes directory (must be located relative to the input directory). The data directory can be assigned to change the directory for global data object available to all templates (must be located relative to the input directory). It should be noted that, if a file is deleted from the input directory, it may not be deleted from the output site directory, so it is good practice to occasionally deleted the output site directory and re-build the project completely.

Configuration setup as an object literal:
module.exports = {
			    // Include custom configuration changes.
			};
Configuration setup as a function:
module.exports = function (eleventyConfig) {
			    // Add possible helper methods available from the Configuration API.
			    // This includes filters, shortcodes, functions, tags, plugins, etc.
			    return {
			        // Include custom configuration changes.
			    };
			};
Change the locations for default directories:
module.exports = {
			    dir: {
			        input: ".",              // Object key: dir.input
			        output: "_site",         // Object key: dir.output
			        includes: "_includes",   // Object key: dir.includes
			        layouts: "_includes",    // Object key: dir.layouts
			        data: "_data",           // Object key: dir.data
			    }
			};

The subset of format types of the templates can be specified which should be processed and transformed (file extensions should be considered case-insensitive, where the supported templates include .html, .liquid, .ejs, .md, .hbs, .mustache, .haml, .pug, .njk, and .11ty.js). The template engine can be set to process data before transforming to JSON, markdown before transforming to HTML, and HTML before transforming to modified HTML (use false to avoid pre-processing and only transform or passthrough copy the content). The output can be effectively deployed to a sub-directory with a path prefix which is inserted at the beginning of all absolute URL hyperlinks (does not affect file structure). A transform can be used to modify the output from a template. A linter can be used to analyse the output from a template without modifying it.

Change the transformed format types:
module.exports = {
			    templateFormats: ["html", "liquid", "ejs", "md", "hbs", "mustache", "haml", "pug", "njk", "11ty.js"]
			};

module.exports = function (eleventyConfig) {
			    eleventyConfig.setTemplateFormats ("html,liquid,ejs,md,hbs,mustache,haml,pug,njk,11ty.js");
			};

module.exports = function (eleventyConfig) {
			    eleventyConfig.setTemplateFormats (["html", "liquid", "ejs", "md", "hbs", "mustache", "haml", "pug", "njk", "11ty.js"]);
			};
Change the default template engine:
module.exports = {
			    "dataTemplateEngine": "liquid"
			    "markdownTemplateEngine": "md"
			    "htmlTemplateEngine": "html"
			};
Insert a path prefix for the sub-directory:
module.exports = {
			    pathPrefix: "/base-sub-directory/"
			};
Transform the output from the template with modifications:
module.exports = function (eleventyConfig) {
			    eleventyConfig.addTransform ("name", function (content, outputPath) {});
			    eleventyConfig.addTransform ("async-name", async function (content, outputPath) {});
			    eleventyConfig.addTransform ("name", function (content) {
			        console.log ( this.inputPath );
			        console.log ( this.outputPath );
			    });
			};
Linter the output from the template without modifications:
module.exports = function (eleventyConfig) {
			    eleventyConfig.addLinter ("name", function (content, inputPath, outputPath) {});
			    eleventyConfig.addLinter ("async-name", async function (content, inputPath, outputPath) {});
			    eleventyConfig.addLinter ("name", function (content) {
			        console.log ( this.inputPath );
			        console.log ( this.outputPath );
			    });
			};

By default, Eleventy will only search for files in the input directory which have a file extension listed in the subset of format types of the templates which should be processed and transformed. If a file format is included in this list and is recognized as a valid format types of the templates, the corresponding files will be processed and then copied while maintaining the directory structure. If a file format is included in this list but is not recognized as a valid format types of the templates, the corresponding files will be directly copied without processing while maintaining the directory structure. If a file format is not included in this list, the corresponding files will be ignored and they will not be copied it to the output directory (if they are to be copied, it is necessary to add .css, .js, .svg, .png, .jpg, etc).

However, searching the entire directory structure for files to copy based on the file extensions is not optimal with large directory structures and may lead to extended waiting times. Instead, a passthrough file copy can be used to directly copy a specified directory or file. This will only require searching up to the directory being copied, such that it is not necessary to search in the remaining sub-directories. It is also possible to use globs to target files with a specific trait (it should be noted that this method is slower than manually specifying files and directories individually, as it is still necessary to search the entire directory structure and copy files individually). To passthrough file copy all the files in the input directory, it is currently necessary to use the command line argument (although this is not usually recommended for security reasons).

Include other file types to be copied with the transformed format types:
module.exports = {
			    templateFormats: ["html", "css", "js", "svg", "png", "jpg"]
			};

module.exports = function (eleventyConfig) {
			    eleventyConfig.setTemplateFormats ("html,css,js,svg,png,jpg");
			};

module.exports = function (eleventyConfig) {
			    eleventyConfig.setTemplateFormats (["html", "css", "js", "svg", "png", "jpg"]);
			};
Manual passthrough file copy to the output directory:
module.exports = function (eleventyConfig) {
			    eleventyConfig.addPassthroughCopy ("path/folder/"); 
			    eleventyConfig.addPassthroughCopy ("path/file": "target/file"); 
			    eleventyConfig.addPassthroughCopy ("**/*.(!md)": "target/"); 
			    eleventyConfig.addPassthroughCopy ("**/*.{svg,png,jpg}": "target/"); 
			};

By default, Eleventy will spider the dependencies of the configuration file, JavaScript Templates, and JavaScript Data Files to watch those files. Other watch targets can also be added manually as files or directories and a build will be triggered when one of these files or directories changes (useful if Eleventy is not directly aware of any external file dependencies). The process can be run without logging information to the console, where it is executed in quiet mode and does not show each file which is processed with the corresponding output file. The options for Browsersync can be directly overridden for the hot-reloading web server. A delay can be used to set a waiting period between consecutive builds (useful with task runners, such as Gulp and Grunt).

Watch Default JavaScript Dependencies
module.exports = function (eleventyConfig) {
		    eleventyConfig.setWatchJavaScriptDependencies (true);
		};
Add Custom Watch Targets
module.exports = function (eleventyConfig) {
		    eleventyConfig.addWatchTarget ("./src/scss/");
		};
Enable Quiet Mode To Reduce Console Noise
module.exports = function (eleventyConfig) {
		    eleventyConfig.setQuiteMode (true);
		};
Override Browsersync Server Options
module.exports = function (eleventyConfig) {
		    eleventyConfig.setBrowserSyncConfig ({
		        notify: true
		    });
		};
Delay Time Between Consecutive Builds (Milliseconds)
module.exports = function (eleventyConfig) {
		    eleventyConfig.setWatchThrottleWaitTime (0);
		};

Construction Overview

The overall construction of Eleventy features templates, layouts, and includes. Additionally, filters and shortcodes can be defined for ... .

A template is a generic framework for the content files. In other words, a template is the initial file in the supported language which is then processed to created the page for the output site. For example, once the template is processed, it is output as the final page which is displayed on the output site. As mentioned, the supported templates include .html, .liquid, .ejs, .md, .hbs, .mustache, .haml, .pug, .njk, and .11ty.js. The front matter is defined at the beginning of a template and contains data by defining objects for the template, such as specifying the layout, title, and date to be used for the page. The layouts and includes may sometimes be referred to as template files.

A layout is a special template used to wrap other content files. For example, a template refers to the pre-processed content of the page to be generated which is then inserted into a layout at the specified slot for the content of the template. In other words, a layout is a parent template to a child template which contains the pre-processed content of the page. The front matter can also be defined at the beginning of a layout and it will be used for the subsequent templates unless it is overridden by the template.

An include ... .

A filter modifies or transforms content in some way specific to the content type. ...For example, you may create a filter intended for strings to uppercase them. Or you might have a filter intended to be used on arrays to alter what is returned, like picking a random item... . It needs to be defined within the origin configuration file and is available for use with Nunjucks, Liquid, Handlebars, and JavaScript templating. There are several built-in filters provided: https://www.11ty.dev/docs/filters/.

A shortcode is ... . It needs to be defined within the origin configuration file and is available for use with Nunjucks, Liquid, Handlebars, and JavaScript templating.

Front Matter

To parse the front matter, the Gray Matter package from NPM (automatically installed) is used with support for formats of YAML (default), JSON, TOML, Coffee Front-Matter, and arbitrary JavaScript. This conventionally includes objects for the layout, title, date, tags, and permalinks. The front matter assigned locally for a page will override objects defined at higher levels on the chain of layouts. If the front matter is not assigned locally for a page, then the objects defined at higher levels on the chain of layouts will be used. The front matter will be stored in the data structure associated with the page.

The date object overrides the default date which is used for sorting in a collection. The value can be assigned as Last Modified to automatically resolve to the last modified date of the file; Created to automatically resolve to the creation date of the file (default if the date item is omitted, unless the file name contains a date); or YYYY-MM-DD to use the YAML format at midnight in the coordinated universal time zone if the time is omitted. If the date object is requested, it will automatically be converted to a string for display.

Customise Gray Matter Parsing
module.exports = functions (eleventyConfig) {
		    eleventyConfig.setFrontMatterParsingOptions ({
		        ...
		    });
		};
Conventional Front Matter With YAML
---
		layout: Default
		title: Example Page
		date: Created
		tags: ["category-one", "category-two"]
		author: Some Person
		---
Example Data Generated For Page
{
		    inputPath: './directory/example.md',
		    fileSlug: 'example',
		    outputPath: './_site/directory/example/index.html',
		    url: '/directory/example/',
		    date: 2021-07-20T04:10:17.000Z,
		    data: {layout: 'Default', title: 'Example Page', tags: ['category-one', 'category-two'], date: 'Last Modified', author: 'Some Person'},
		    templateContent: '<h1>This is the title</h1> <p>This is the content...'
		}

It is also possible to override the language of the template using the front matter, which will be applied even if the file type is different from the specified language. This will affect the assigned rendering engine of the template. To completely avoid pre-processing, use false which will only transform or passthrough copy the content.

Template Language Override With YAML
templateEngineOverride: liquid

templateEngineOverride: njk,md

templateEngineOverride: false

The permalink object can be used to remap a template to an alternative output path. It is allowable to use variables and filters in the permalink which will be parsed with the current or assigned rendering engine of the template (ensure quotation marks are used, otherwise the permalink will not be parsed). If false is assigned, the file will still be processed normally, but it will not be written to the output directory as a standalone page. The output directory can be bypassed, such that the permalink is defined from the root directory of the project - this can be useful when writing child templates to the includes directory for re-use in other templates. The final format can also be changed by using a different file extension in the permalink.

Permalink Object With YAML
permalink: "new-path/sub-directory/"

permalink: "new-path/sub-directory/index.html"

permalink: "new-path/{{ page.date }}/index.html"
Bypass Output Directory
permalinkBypassOutputDir: true
permalink: "_includes/sub-directory/index.html"
Change File Extension
permalink: "new-path/sub-directory/index.json"

Tags And Collections

A tag is implicitly defined in the front matter to group pages within collections, where pages with the same tag will form a single collection. A page may have multiple tags defined to place it in multiple collections. The default sorting algorithm in a collection is ascending order using the date object of the input file and, if the date objects are identical for multiple files, then alphabetically based on the the full path of the input file. Alternatively, the sorting can be reversed to sort in descending order using a filter. The use of collections is useful when creating lists of pages within the same collection. If desired, it is also possible to directly access all of the collections without considering the tags, while it is also possible to completely exclude a page from all of the collections.

A page in a collection has a data structure which consists of the input path, file slug, output path, URL, date, data, and template content. The input path is the full path to the source input file (including the path to the input directory). The file slug is mapped from the input path as a short name from the file name and useful for creating clean permalinks. The output path is the full path to the output file to be written for the content. The URL is the link to the piece of content. The date is the resolved date used for sorting. The data contains the objects defined in the front matter from the data cascade of the page and its layouts. The template content is the rendered content of the page. In a sense, this data can be seen to be stored as a child to the collection.

Tag-Based Collections Implicit Definition
tags: Category

tags: ["category-one", "category-two", "category-three"]

tags:
		  - category-one
		  - category-two
		  - category-three
List Collection With ...Numjaks... (Reversed)
{% for page in collections.post %}
		    {{ page.url }}
		    {{ page.date }}
		    {{ page.data.title }}
		    {{ page.data.tags }}
		    {{ page.data.author }}
		{% endfor %}
Access All Collections (Unless Explicitly Ignored)
{% for post in collections.all %}
		    ...
		{% endfor %}
Explicitly Exclude From Collection
---
		eleventyExcludeFromCollections: true
		---
Example Data Generated For Collection
{
		    "all": [...],
		    "category-one": [
		        {
		            inputPath: './directory/example.md',
		            fileSlug: 'example',
		            outputPath: './_site/directory/example/index.html',
		            url: '/directory/example/',
		            date: 2021-07-20T04:10:17.000Z,
		            data: { layout: 'Default', title: 'Example Page', tags: ['category-one', 'category-two'], date: 'Last Modified', author: 'Some Person' },
		            templateContent: '<h1>This is the title</h1> <p>This is the content...'
		        },
		        ...
		    ],
		    "category-two": [...],
		}

In addition to the collections built from tags, a collection can also be explicitly defined through custom filtering and sorting. This is more advanced and defined through the configuration file. Currently, this has not yet been considered for personal use.

Layouts And Includes

The desired layout for the content is assigned in the front matter and created in the includes directory by default, where all supported template formats will be cycled through to look for a matching file. The template language used in the layout does not have to match the template language of the contents for the page. The layout can also contain front matter which will be merged with the front matter of the content, where the data of the content takes precedence if there are conflicting objects (for a chain of layouts, the front matter will have a higher priority the closer it is to the content).

For a Nunjucks or Liquid layout, the content value will be populated with the contents (also, in Nunjucks, the safe filter is usually used to avoid double-escaping the output, but this is automatically performed by default in Liquid and escape is needed to allow double-escaping the output). A chain of layouts can also be created, where a certain layout will use another specific layout defined in its front matter. The template language used in each of the cases does not have to match across layouts or templates.

Set Front Matter To Use Layout
layout: example
Simple Layout With Nunjucks Processing
---
		title: Simple Layout
		---
		<!doctype html>
		<html lang = "en">
		    <head>
		        <meta charset = "utf-8">
		        <title>{{ title }}</title>
		    </head>
		    <body>
		        {{ content | safe }}
		    </body>
		</html>

Figure illustrating chain of content > layout > layout.

As mentioned, the directory for the layouts can be changed to a custom directory defined relative to the input directory (can be stored in a sub-directory of the directory containing the layouts). In the configuration file, a name for a layout can be mapped to a different name as an alias. This mapping can be useful instead of rewriting the values in the front matter of the content if the name were to change in the future.

Create Layout Alias
module.exports = functions (eleventyConfig) {
		    eleventyConfig.addLayoutAlias ('layout-name', 'sub-directory/alias-name.njk');
		};

Include ...

Filters And Shortcodes

Currently, filters and shortcodes are not being considered for personal use. Generally, the scope of filters includes using the built-in filters and creating universal filters. Generally, the scope of shortcodes includes ... .

Data Sources

https://www.11ty.dev/docs/data-cascade/ https://benmyers.dev/blog/eleventy-data-cascade/

The data is merged in a cascade, where the order of priority for sources is computed data, front matter data in a template, front matter data in layouts, template data files, directory data files, global data files, and custom global data.